package com.tianji.learning.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.tianji.api.client.remark.RemarkClient;
import com.tianji.api.dto.remark.ChangedTimesDTO;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.dto.ReplyDTO;
import com.tianji.learning.domain.po.InteractionQuestion;
import com.tianji.learning.domain.po.InteractionReply;
import com.tianji.learning.domain.query.ReplyPageQuery;
import com.tianji.learning.domain.vo.ReplyVO;
import com.tianji.learning.executor.ContextAwareExecutor;
import com.tianji.learning.mapper.InteractionQuestionMapper;
import com.tianji.learning.mapper.InteractionReplyMapper;
import com.tianji.learning.service.IInteractionReplyService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 互动问题的回答或评论 服务实现类
 * </p>
 *
 * @author huanmo
 * @since 2025-09-18
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class InteractionReplyServiceImpl extends ServiceImpl<InteractionReplyMapper, InteractionReply> implements IInteractionReplyService {

    private final InteractionReplyMapper interactionReplyMapper;
    private final InteractionQuestionMapper interactionQuestionMapper;
    private final InteractionQuestionServiceImpl interactionQuestionServiceImpl;
    @Qualifier("redisObjectTemplate")
    private final RedisTemplate<String, Object> redisTemplate;
    private static final String REPLY_CACHE_KEY_PREFIX = "reply:info:";
    @Qualifier("likedRecordServiceRedisImpl")
    private final RemarkClient remarkClient;
    private final ContextAwareExecutor contextAwareExecutor;
    // 本地缓存（一级缓存）
    private final Cache<Long, InteractionReply> localCache = Caffeine.newBuilder()
            .maximumSize(1000)
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .build();
    private final RabbitMqHelper mqHelper;

    private String getReplyCacheKey(Long replyId) {
        return REPLY_CACHE_KEY_PREFIX + replyId;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addReply(ReplyDTO replyDTO) {
        // 1. 数据校验和转换
        if (replyDTO == null) {
            throw new BizIllegalException("回复数据不能为空");
        }
        // 2. 获取当前用户信息
        Long currentUserId = UserContext.getUser();
        if (currentUserId == null) {
            log.warn("用户未登录");
            throw new BizIllegalException("请先登录");
        }
        // 3. 数据转换和设置基础信息
        InteractionReply interactionReply = BeanUtil.copyProperties(replyDTO, InteractionReply.class);
        interactionReply.setUserId(currentUserId);
        interactionReply.setCreateTime(LocalDateTime.now());
        // 4. 写入回答或评论
        int insertCount = interactionReplyMapper.insert(interactionReply);
        if (insertCount <= 0) {
            log.error("插入回复失败，replyDTO: {}", replyDTO);
            throw new DbException("发表回复失败");
        }
        log.debug("成功插入回复，ID: {}", interactionReply.getId());
        // 5. 判断是否是回答（answerId为空）  answerId：回复的上级回答id，没有可不填
        if (replyDTO.getAnswerId() == null) {
            // 5.1. 是回答 - 更新问题表
            UpdateWrapper<InteractionQuestion> questionUpdateWrapper = new UpdateWrapper<>();
            questionUpdateWrapper.eq("id", replyDTO.getQuestionId())
                    .set("latest_answer_id", interactionReply.getId())
                    .setSql("answer_times = answer_times + 1");
            int updateCount = interactionQuestionMapper.update(null, questionUpdateWrapper);
            if (updateCount <= 0) {
                log.warn("更新问题表失败，questionId: {}", replyDTO.getQuestionId());
                // 可以根据业务需求决定是否抛出异常
            }
            log.debug("更新问题表成功，设置最新回答ID: {}", interactionReply.getId());
        } else {
            // 5.2. 是评论 - 需要进一步判断是回复回答还是回复评论
            UpdateWrapper<InteractionReply> replyUpdateWrapper = new UpdateWrapper<>();
            if (replyDTO.getTargetReplyId() == null) {
                // 5.2.1. 回复回答（一级评论）
                // 更新父回答的评论数量
                replyUpdateWrapper.eq("id", replyDTO.getAnswerId())
                        .setSql("reply_times = reply_times + 1");
                int updateCount = interactionReplyMapper.update(null, replyUpdateWrapper);
                if (updateCount <= 0) {
                    log.warn("更新回答评论数失败，targetReplyId: {}", replyDTO.getAnswerId());
                }
                log.debug("更新回答评论数成功，targetReplyId: {}", replyDTO.getAnswerId());
            } else {
                // 5.2.2. 回复评论（二级评论）
                // 更新父评论的回复数量
                replyUpdateWrapper.eq("id", replyDTO.getTargetReplyId())
                        .setSql("reply_times = reply_times + 1");
                int updateCount = interactionReplyMapper.update(null, replyUpdateWrapper);
                if (updateCount <= 0) {
                    log.warn("更新回答评论数失败，answerId: {}", replyDTO.getTargetReplyId());
                }
                log.debug("更新回答评论数成功，answerId: {}", replyDTO.getTargetReplyId());
            }
        }
        // 5.3. 处理@用户逻辑（如果有targetUserId）
        if (replyDTO.getTargetUserId() != null) {
            try {
                // 这里可以发送通知消息给被@的用户
                log.debug("发送@通知成功，targetUserId: {}", replyDTO.getTargetUserId());
            } catch (Exception e) {
                log.warn("发送@通知失败", e);
                // 通知发送失败不影响主流程
            }
        }
        // 6. 判断提交者身份（是否是学生）
        // 通常应该从用户服务获取用户身份，而不是依赖前端传参
        try {
            UserDTO user = interactionQuestionServiceImpl.queryUserByIdWithCache(currentUserId);
            if (user != null && user.getType().equals(2)) {
                // 6.1. 学生提交 - 标记问题状态为未查看
                UpdateWrapper<InteractionQuestion> statusUpdateWrapper = new UpdateWrapper<>();
                statusUpdateWrapper.eq("id", replyDTO.getQuestionId())
                        .set("status", 0);
                int updateCount = interactionQuestionMapper.update(null, statusUpdateWrapper);
                if (updateCount <= 0) {
                    log.warn("更新问题状态失败，questionId: {}", replyDTO.getQuestionId());
                }
                log.debug("标记问题状态为未查看，questionId: {}", replyDTO.getQuestionId());
            }
        } catch (Exception e) {
            log.warn("查询用户信息失败，无法确定用户身份", e);
            // 查询用户信息失败不影响主流程
        }
        // 7. 所有业务逻辑完成后，再发送MQ消息（放在最后）
        // 保存积分明细记录
        // 注册事务同步（必须在事务提交前调用）
        TransactionSynchronizationManager.registerSynchronization(
                new TransactionSynchronization() {
                    @Override
                    public void afterCommit() {
                        try {
                            mqHelper.send(
                                    MqConstants.Exchange.LEARNING_EXCHANGE,
                                    MqConstants.Key.WRITE_REPLY,
                                    currentUserId
                            );
                            log.debug("MQ消息发送成功，userId: {}", currentUserId);
                        } catch (Exception e) {
                            log.error("MQ消息发送失败，userId: {}", currentUserId, e);
                            // MQ发送失败不影响主事务，需要其他补偿机制
                        }
                    }
                }
        );
        log.info("回复添加成功，用户ID: {}, 问题ID: {}", currentUserId, replyDTO.getQuestionId());
    }

    @Override
    public PageDTO<ReplyVO> queryReplyPage(ReplyPageQuery query) {
        // 1.分页查询
        // 不建议添加缓存：实时性要求高，写多读少，查询条件多变，
        Long questionId = query.getQuestionId();
        Long answerId = query.getAnswerId();
        Page<InteractionReply> page = lambdaQuery()
                .eq(questionId != null, InteractionReply::getQuestionId, questionId)
                .eq(answerId != null, InteractionReply::getAnswerId, answerId)
                // 只查询未隐藏的记录
                .eq(InteractionReply::getHidden, false)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<InteractionReply> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        // 2.准备VO需要的数据：回复者信息、目标用户名字、点赞、被回复的评论
        Set<Long> userIds = new HashSet<>();
        Set<Long> targetUserIds = new HashSet<>();
        // 获取当前评论的id，用于查询点赞状态
        Set<Long> replyIds = new HashSet<>();
        // 获取被回复的那条评论，查看该评论是否为匿名的。如果是，回复这条评论的时候。会显示：“回复匿名用户”
        Set<Long> targetReplyIds = new HashSet<>();
        for (InteractionReply r : records) {
            userIds.add(r.getUserId());
            targetUserIds.add(r.getTargetUserId());
            replyIds.add(r.getId());
            targetReplyIds.add(r.getTargetReplyId());
        }
        System.out.println("replyIds: " + replyIds);
        List<Long> replyIdList = new ArrayList<>(replyIds);
        System.out.println("replyIdList: " + replyIdList);
        // 一键查询 回复者信息、目标用户名字、点赞
        Result result = getResult(userIds, targetUserIds, replyIdList);
        CompletableFuture<Map<Long, InteractionReply>> targetReplyFuture = contextAwareExecutor.supplyAsync(() -> {
            try {
                // 采用多级缓存
                return batchGetRepliesWithCache(targetReplyIds);
            } catch (Exception e) {
                log.warn("查询评论信息失败，targetReplyIds: {}", targetReplyIds, e);
                return Collections.emptyMap();
            }
        });
        // 3.等待所有查询完成
        try {
            Map<Long, UserDTO> userMap = result.userFuture.get(5, TimeUnit.SECONDS);
            Map<Long, UserDTO> targetUserMap = result.targetUserFuture.get(5, TimeUnit.SECONDS);
            Set<Long> likedReplyIds = result.likedReplyIdsFuture.get(5, TimeUnit.SECONDS);
            Map<Long, InteractionReply> targetReplyMap = targetReplyFuture.get(5, TimeUnit.SECONDS);
            // 4. 使用查询结果继续处理...
            // 封装VO
            List<ReplyVO> voList = new ArrayList<>(records.size());
            for (InteractionReply r : records) {
                // 4.1.将PO转VO，属性拷贝
                ReplyVO vo = BeanUtils.copyBean(r, ReplyVO.class);
                // Java对象是引用传递
                // 后续的属性设置会反映到已添加到列表的对象中
                voList.add(vo);
                // 4.2.封装回复者信息
                if (!r.getAnonymity()) {
                    UserDTO userDTO = userMap.get(r.getUserId());
                    if (userDTO != null) {
                        vo.setUserId(userDTO.getId());
                        vo.setUserName(userDTO.getName());
                        vo.setUserIcon(userDTO.getIcon());
                        vo.setUserType(userDTO.getType());
                    }
                }
                // 4.3.封装被回复者信息
                if (r.getTargetUserId() != null) {
                    // 先检查被回复的评论是否匿名
                    boolean isTargetAnonymous = false;
                    if (r.getTargetReplyId() != null) {
                        // 获取被回复的评论信息
                        InteractionReply targetReply = targetReplyMap.get(r.getTargetReplyId());
                        if (targetReply != null) {
                            // 判断被回复的评论是否匿名
                            isTargetAnonymous = Boolean.TRUE.equals(targetReply.getAnonymity());
                        }
                    }
                    // 如果被回复的评论不是匿名的，才显示被回复者昵称
                    if (!isTargetAnonymous) {
                        UserDTO targetUserDTO = targetUserMap.get(r.getTargetUserId());
                        if (targetUserDTO != null) {
                            vo.setTargetUserName(targetUserDTO.getName());
                        }
                    } else {
                        // 如果被回复的评论是匿名的，显示"匿名用户"
                        vo.setTargetUserName("匿名用户");
                    }
                }
                // 4.4.设置点赞状态
                vo.setLiked(likedReplyIds.contains(r.getId()));
                // 该词条不对用户端展示
                vo.setHidden(null);
            }
            return PageDTO.of(page, voList);
        } catch (TimeoutException e) {
            log.error("查询超时，userIds: {}", userIds, e);
            throw new BizIllegalException("查询超时，请重试");
        } catch (ExecutionException | InterruptedException e) {
            log.error("并行查询失败", e);
            throw new BizIllegalException("查询失败，请重试");
        }
    }

    private CompletableFuture<Map<Long, UserDTO>> getUserFuture(Set<Long> userIds, String s) {
        // 2.2.并行查询相关数据
        return contextAwareExecutor.supplyAsync(() -> {
            try {
                // 添加空值检查
                if (CollUtils.isEmpty(userIds)) {
                    log.debug("用户ID集合为空，直接返回空Map");
                    return Collections.emptyMap();
                }
                List<UserDTO> users = interactionQuestionServiceImpl.queryUserByIdsWithCache(userIds);
                return CollUtils.isNotEmpty(users) ?
                        users.stream().collect(Collectors.toMap(UserDTO::getId, u -> u)) :
                        Collections.emptyMap();
            } catch (Exception e) {
                log.warn(s, userIds, e);
                return Collections.emptyMap();
            }
        });
    }

    @Override
    public PageDTO<ReplyVO> queryReplyPageAdmin(ReplyPageQuery query) {
        // 1.分页查询
        // 不建议添加缓存：实时性要求高，写多读少，查询条件多变，
        Long questionId = query.getQuestionId();
        Long answerId = query.getAnswerId();
        Page<InteractionReply> page = lambdaQuery()
                .eq(questionId != null, InteractionReply::getQuestionId, questionId)
                .eq(answerId != null, InteractionReply::getAnswerId, answerId)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<InteractionReply> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        // 2.准备VO需要的数据：回复者信息、目标用户名字、点赞
        Set<Long> userIds = new HashSet<>();
        Set<Long> targetUserIds = new HashSet<>();
        // 获取当前评论的id，用于查询点赞状态
        Set<Long> replyIds = new HashSet<>();
        for (InteractionReply r : records) {
            userIds.add(r.getUserId());
            targetUserIds.add(r.getTargetUserId());
            replyIds.add(r.getId());
        }
        List<Long> replyIdList = new ArrayList<>(replyIds);
        // 一键查询 回复者信息、目标用户名字、点赞
        Result result = getResult(userIds, targetUserIds, replyIdList);
        // 3.等待所有查询完成
        try {
            Map<Long, UserDTO> userMap = result.userFuture.get(5, TimeUnit.SECONDS);
            Map<Long, UserDTO> targetUserMap = result.targetUserFuture.get(5, TimeUnit.SECONDS);
            Set<Long> likedReplyIds = result.likedReplyIdsFuture.get(5, TimeUnit.SECONDS);
            // 4. 使用查询结果继续处理...
            // 封装VO
            List<ReplyVO> voList = new ArrayList<>(records.size());
            for (InteractionReply r : records) {
                // 4.1.将PO转VO，属性拷贝
                ReplyVO vo = BeanUtils.copyBean(r, ReplyVO.class);
                // Java对象是引用传递
                // 后续的属性设置会反映到已添加到列表的对象中
                voList.add(vo);
                // 4.2.封装回复者信息
                UserDTO userDTO = userMap.get(r.getUserId());
                if (userDTO != null) {
                    vo.setUserId(userDTO.getId());
                    vo.setUserName(userDTO.getName());
                    vo.setUserIcon(userDTO.getIcon());
                    vo.setUserType(userDTO.getType());
                }
                // 4.3.封装被回复者信息
                UserDTO targetUserDTO = targetUserMap.get(r.getTargetUserId());
                if (targetUserDTO != null) {
                    vo.setTargetUserName(targetUserDTO.getName());
                }
                // 4.4.设置点赞状态
                vo.setLiked(likedReplyIds.contains(r.getId()));
            }
            return PageDTO.of(page, voList);
        } catch (TimeoutException e) {
            log.error("查询超时，userIds: {}", userIds, e);
            throw new BizIllegalException("查询超时，请重试");
        } catch (ExecutionException | InterruptedException e) {
            log.error("并行查询失败", e);
            throw new BizIllegalException("查询失败，请重试");
        }
    }

    private Result getResult(Set<Long> userIds, Set<Long> targetUserIds, List<Long> replyIdList) {
        CompletableFuture<Map<Long, UserDTO>> userFuture = getUserFuture(userIds, "查询用户信息失败，userIds: {}");
        CompletableFuture<Map<Long, UserDTO>> targetUserFuture = getUserFuture(targetUserIds, "查询目标用户信息失败，targetUserIds: {}");
        // 新增：查询点赞状态的Future
        CompletableFuture<Set<Long>> likedReplyIdsFuture = contextAwareExecutor.supplyAsync(() -> {
            try {
                log.info("查询点赞状态，replyIds: {}", replyIdList);
                Set<Long> result = remarkClient.isBizLiked(replyIdList);
                log.info("点赞查询结果: {}", result);
                return result;
            } catch (Exception e) {
                log.warn("查询点赞状态失败", e);
                return Collections.emptySet();
            }
        });
        return new Result(userFuture, targetUserFuture, likedReplyIdsFuture);
    }

    private static class Result {
        public final CompletableFuture<Map<Long, UserDTO>> userFuture;
        public final CompletableFuture<Map<Long, UserDTO>> targetUserFuture;
        public final CompletableFuture<Set<Long>> likedReplyIdsFuture;

        public Result(CompletableFuture<Map<Long, UserDTO>> userFuture, CompletableFuture<Map<Long, UserDTO>> targetUserFuture, CompletableFuture<Set<Long>> likedReplyIdsFuture) {
            this.userFuture = userFuture;
            this.targetUserFuture = targetUserFuture;
            this.likedReplyIdsFuture = likedReplyIdsFuture;
        }
    }

    @Override
    @Transactional
    public void isHidden(long id, boolean hidden) {
        // 1.根据id查询问题
        InteractionReply reply = getById(id);
        if (reply == null) {
            throw new BadRequestException("该评论不存在");
        }
        // 2.如果状态已经相同，直接返回
        if (reply.getHidden().equals(hidden)) {
            log.debug("评论{}已经是{}状态，无需操作", id, hidden ? "隐藏" : "显示");
            return;
        }
        // 3.先隐藏评论下的评论（如果有的话）
        LambdaUpdateWrapper<InteractionReply> replyWrapper = new LambdaUpdateWrapper<>();
        replyWrapper
                .eq(InteractionReply::getTargetReplyId, id)
                .or()
                .eq(InteractionReply::getAnswerId, id)
                .set(InteractionReply::getHidden, hidden);
        interactionReplyMapper.update(null, replyWrapper);
        // 4.更新评论本身的隐藏状态
        boolean updateReply = lambdaUpdate()
                .eq(InteractionReply::getId, id)
                .set(InteractionReply::getHidden, hidden)
                .update();
        if (!updateReply) {
            log.error("更新评论[{}]隐藏状态失败", id);
            throw new DbException("更新评论状态失败");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateBatchByBizId(List<ChangedTimesDTO> changedTimesDTOs) {
        for (ChangedTimesDTO dto : changedTimesDTOs) {
            if (dto.getChangedTimes() > 0) {
                baseMapper.increaseLikedTimes(dto.getBizId(), dto.getChangedTimes());
            } else {
                baseMapper.decreaseLikedTimes(dto.getBizId(), Math.abs(dto.getChangedTimes()));
            }
        }
        return true;
    }

    /**
     * 多级缓存获取回复信息
     */
    public InteractionReply getReplyByIdWithCache(Long replyId) {
        if (replyId == null) {
            return null;
        }
        try {
            // 1. 检查一级缓存（本地）
            InteractionReply reply = localCache.getIfPresent(replyId);
            if (reply != null) {
                log.debug("一级缓存命中，replyId: {}", replyId);
                return reply;
            }
            // 2. 检查二级缓存（Redis）
            String cacheKey = getReplyCacheKey(replyId);
            reply = (InteractionReply) redisTemplate.opsForValue().get(cacheKey);
            if (reply != null) {
                log.debug("二级缓存命中，replyId: {}", replyId);
                // 回填一级缓存
                localCache.put(replyId, reply);
                return reply;
            }
            // 3. 缓存未命中，查询数据库
            log.debug("缓存未命中，从数据库查询，replyId: {}", replyId);
            reply = interactionReplyMapper.selectById(replyId);
            // 4. 更新缓存
            updateCacheAsync(replyId, reply);
            return reply;
        } catch (Exception e) {
            log.warn("多级缓存查询失败，直接查询数据库，replyId: {}", replyId, e);
            return interactionReplyMapper.selectById(replyId);
        }
    }

    /**
     * 批量多级缓存获取回复信息
     */
    public Map<Long, InteractionReply> batchGetRepliesWithCache(Set<Long> replyIds) {
        log.debug("开始批量获取回复数据，replyIds: {}", replyIds);
        if (CollUtils.isEmpty(replyIds)) {
            log.debug("replyIds为空，直接返回空结果");
            return Collections.emptyMap();
        }

        // 清理已有的一级缓存（测试用）
//        evictReplyCache(replyIds);

        Map<Long, InteractionReply> result = new HashMap<>();
        List<Long> missingIds = new ArrayList<>();
        // 1. 先从一级缓存获取
        for (Long replyId : replyIds) {
//            InteractionReply reply = localCache.getIfPresent(replyId);
            InteractionReply reply = null; // 禁用一级缓存
            if (reply != null) {
                log.debug("从一级缓存获取到回复数据，replyId: {}", replyId);
                result.put(replyId, reply);
            } else {
                missingIds.add(replyId);
            }
        }
        // 2. 如果一级缓存都有，直接返回
        if (missingIds.isEmpty()) {
            log.debug("所有数据都从一级缓存获取，直接返回结果");
            return result;
        }
        log.debug("一级缓存缺失的数据量: {}", missingIds.size());
        // 3. 从二级缓存（Redis）获取缺失的
        List<Object> cachedObjects = redisTemplate.opsForValue()
                .multiGet(missingIds.stream().map(this::getReplyCacheKey).collect(Collectors.toList()));
        log.debug("从Redis获取缓存数据");
        List<Long> dbQueryIds = new ArrayList<>();
        for (int i = 0; i < missingIds.size(); i++) {
            Long replyId = missingIds.get(i);
            // 检查Redis中是否有数据
            if (cachedObjects != null && i < cachedObjects.size() && cachedObjects.get(i) != null) {
                Object cachedObject = cachedObjects.get(i);
                try {
                    // 反序列化Redis中的数据
                    InteractionReply reply = deserializeFromRedis(cachedObject);
                    // 检查是否是空值占位符
                    if (reply.getId() != null) {
                        log.debug("从Redis缓存获取到回复数据，replyId: {}", replyId);
                        result.put(replyId, reply);
                        // 回填一级缓存
                        localCache.put(replyId, reply);
                    } else {
                        log.debug("Redis中存在空值占位符，需要查询数据库，replyId: {}", replyId);
                        // 空值占位符，需要查询数据库
                        dbQueryIds.add(replyId);
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            } else {
                log.debug("Redis中未找到数据，需要查询数据库，replyId: {}", replyId);
                // Redis中没有找到，需要查询数据库
                dbQueryIds.add(replyId);
            }
        }
        // 4. 从数据库查询剩余的
        if (CollUtils.isNotEmpty(dbQueryIds)) {
            log.debug("开始从数据库查询数据，查询ID数量: {}", dbQueryIds.size());
            List<InteractionReply> dbReplies = interactionReplyMapper.selectBatchIds(dbQueryIds);
            log.debug("数据库查询完成，获取到{}条数据", dbReplies.size());
            // 创建ID到实体的映射，便于查找
            Map<Long, InteractionReply> dbReplyMap = dbReplies.stream()
                    .filter(Objects::nonNull)
                    .collect(Collectors.toMap(InteractionReply::getId, r -> r));
            for (Long replyId : dbQueryIds) {
                InteractionReply reply = dbReplyMap.get(replyId);
                if (reply != null) {
                    log.debug("从数据库获取到回复数据，replyId: {}", reply.getId());
                    result.put(reply.getId(), reply);
                    // 异步更新缓存
                    updateCacheAsync(reply.getId(), reply);
                } else {
                    log.debug("数据库中也未找到数据，将缓存空值，replyId: {}", dbQueryIds.get(0));
                    // 数据库也没有，缓存空值防止穿透
                    updateCacheAsync(dbQueryIds.get(0), null);
                }
            }
        }
        log.debug("批量获取回复数据完成，总共获取到{}条数据", result.size());
        return result;
    }

    /**
     * 从Redis数据反序列化为InteractionReply对象
     */
    private InteractionReply deserializeFromRedis(Object cachedObject) throws IOException {
        if (cachedObject instanceof String) {
            // 如果是JSON字符串，进行反序列化
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.registerModule(new JavaTimeModule());
            objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
            return objectMapper.readValue((String) cachedObject, InteractionReply.class);
        } else if (cachedObject instanceof InteractionReply) {
            // 如果已经是对象，直接返回
            return (InteractionReply) cachedObject;
        } else {
            throw new IOException("不支持的Redis数据类型: " + cachedObject.getClass().getName());
        }
    }

    /**
     * 异步更新缓存
     */
    @Async
    public void updateCacheAsync(Long replyId, InteractionReply reply) {
        try {
            if (reply == null) {
                // 缓存空值防止缓存穿透
                localCache.put(replyId, new InteractionReply());
                redisTemplate.opsForValue().set(
                        getReplyCacheKey(replyId),
                        new InteractionReply(),
                        5, TimeUnit.MINUTES
                );
                return;
            }
            // 更新一级缓存
            localCache.put(replyId, reply);
            // 更新二级缓存
            String cacheKey = getReplyCacheKey(replyId);
            // 手动序列化为JSON字符串
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.registerModule(new JavaTimeModule());
            objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
            String json = objectMapper.writeValueAsString(reply);
            redisTemplate.opsForValue().set(
                    cacheKey,
                    json,
                    RedisConstants.REDIS_EXPIRE_TIME_MINUTES + new Random().nextInt(12),
                    TimeUnit.MINUTES
            );
        } catch (Exception e) {
            log.warn("更新缓存失败，replyId: {}", replyId, e);
        }
    }

    /**
     * 清除缓存
     */
    public void evictReplyCache(Set<Long> replyIds) {
        if (replyIds == null) {
            return;
        }
        try {
            for (Long replyId : replyIds) {
                localCache.invalidate(replyId);
                redisTemplate.delete(getReplyCacheKey(replyId));
            }
        } catch (Exception e) {
            log.debug("已清理{}个本地缓存项", replyIds.size());
        }
    }
}
